NB Cannot use PIT to trigger periodic DMA due to hardware bug

See here.

Try using PDB instead??


In [289]:
import pandas as pd

def get_pdb_divide_params(frequency, F_BUS=int(48e6)):
    mult_factor = np.array([1, 10, 20, 40])
    prescaler = np.arange(8)

    clock_divide = (pd.DataFrame([[i, m, p, m * (1 << p)]
                                  for i, m in enumerate(mult_factor) for p in prescaler],
                                 columns=['mult_', 'mult_factor', 'prescaler', 'combined'])
                    .drop_duplicates(subset=['combined'])
                    .sort_values('combined', ascending=True))
    clock_divide['clock_mod'] = (F_BUS / frequency / clock_divide.combined).astype(int)
    return clock_divide.loc[clock_divide.clock_mod <= 0xffff]

In [304]:
PDB0_IDLY = 0x4003600C  # Interrupt Delay Register
PDB0_SC = 0x40036000  # Status and Control Register
PDB0_MOD = 0x40036004  # Modulus Register

PDB_SC_PDBEIE = 0x00020000  # Sequence Error Interrupt Enable
PDB_SC_SWTRIG = 0x00010000  # Software Trigger
PDB_SC_DMAEN = 0x00008000  # DMA Enable
PDB_SC_PDBEN = 0x00000080  # PDB Enable
PDB_SC_PDBIF = 0x00000040  # PDB Interrupt Flag
PDB_SC_PDBIE = 0x00000020  # PDB Interrupt Enable.
PDB_SC_CONT = 0x00000002  # Continuous Mode Enable
PDB_SC_LDOK = 0x00000001  # Load OK


def PDB_SC_TRGSEL(n): return (((n) & 15) << 8)  # Trigger Input Source Select
def PDB_SC_PRESCALER(n): return (((n) & 7) << 12)  # Prescaler Divider Select
def PDB_SC_MULT(n): return (((n) & 3) << 2)  # Multiplication Factor
def PDB_SC_LDMOD(n): return (((n) & 3) << 18)  # Load Mode Select


# PDB0_IDLY = 1; // the pdb interrupt happens when IDLY is equal to CNT+1
proxy.mem_cpy_host_to_device(PDB0_IDLY, np.uint32(1).tostring())

#             software trigger    enable PDB     continuous
PDB_CONFIG = (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT |  PDB_SC_LDMOD(0))

PDB0_SC_ = (PDB_CONFIG | PDB_SC_PRESCALER(clock_divide.prescaler) |
            PDB_SC_MULT(clock_divide.mult_) | 
            PDB_SC_DMAEN | PDB_SC_LDOK)  # load all new values
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())

In [310]:
clock_divide = get_pdb_divide_params(25).iloc[0]
# PDB0_MOD = (uint16_t)(mod-1);
proxy.mem_cpy_host_to_device(PDB0_MOD, np.uint32(clock_divide.clock_mod).tostring())

PDB0_SC_ = (PDB_CONFIG | PDB_SC_PRESCALER(clock_divide.prescaler) |
            PDB_SC_DMAEN | PDB_SC_MULT(clock_divide.mult_) |
            PDB_SC_SWTRIG)  # start the counter!
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())

In [311]:
PDB0_SC_ = 0
proxy.mem_cpy_host_to_device(PDB0_SC, np.uint32(PDB0_SC_).tostring())

Overview

Use linked DMA channels to perform "scan" across multiple ADC input channels.

After each scan, use DMA scatter chain to write the converted ADC values to a separate output array for each ADC channel. The length of the output array to allocate for each ADC channel is determined by the sample_count in the example below.

See diagram below.

Channel configuration

  • DMA channel $i$ copies conesecutive SC1A configurations to the ADC SC1A register. Each SC1A configuration selects an analog input channel.
    • Channel $i$ is initially triggered by software trigger (i.e., DMA_SSRT = i), starting the ADC conversion for the first ADC channel configuration.
    • Loading of subsequent ADC channel configurations is triggered through minor loop linking of DMA channel $ii$ to DMA channel $i$.
  • DMA channel $ii$ is triggered by ADC conversion complete (i.e., COCO), and copies the output result of the ADC to consecutive locations in the result array.
    • Channel $ii$ has minor loop link set to channel $i$, which triggers the loading of the next channel SC1A configuration to be loaded immediately after the current ADC result has been copied to the result array.
  • After $n$ triggers of channel $i$, the result array contains $n$ ADC results, one result per channel in the SC1A table.
    • N.B., Only the trigger for the first ADC channel is an explicit software trigger. All remaining triggers occur through minor-loop DMA channel linking from channel $ii$ to channel $i$.
  • After each scan through all ADC channels is complete, the ADC readings are scattered using the selected "scatter" DMA channel through a major-loop link between DMA channel $ii$ and the "scatter" channel.

Device

Connect to device


In [260]:
import arduino_helpers.hardware.teensy as teensy
from arduino_rpc.protobuf import resolve_field_values
from teensy_minimal_rpc import SerialProxy
import teensy_minimal_rpc.DMA as DMA
import teensy_minimal_rpc.ADC as ADC
import teensy_minimal_rpc.SIM as SIM
import teensy_minimal_rpc.PIT as PIT


# Disconnect from existing proxy (if available)
try:
    del proxy
except NameError:
    pass

proxy = SerialProxy()
proxy.pin_mode(teensy.LED_BUILTIN, 1)

In [261]:
from IPython.display import display

proxy.update_sim_SCGC6(SIM.R_SCGC6(PDB=True))
sim_scgc6 = SIM.R_SCGC6.FromString(proxy.read_sim_SCGC6().tostring())
display(resolve_field_values(sim_scgc6)[['full_name', 'value']].T)

# proxy.update_pit_registers(PIT.Registers(MCR=PIT.R_MCR(MDIS=False)))
# pit_registers = PIT.Registers.FromString(proxy.read_pit_registers().tostring())
# display(resolve_field_values(pit_registers)[['full_name', 'value']].T)


parent_name
full_name USBDCD SPI0 SPI1 RTC FTM1 FTM0 I2S DMAMUX CRC FTFL ADC0 FLEXCAN0 PIT PDB
value False False False True True True False True False True True False False True

In [266]:
import numpy as np

# CORE_PIN13_PORTSET = CORE_PIN13_BITMASK;
# CORE_PIN13_PORTCLEAR = CORE_PIN13_BITMASK;

#define CORE_PIN13_PORTCLEAR	GPIOC_PCOR
#define CORE_PIN13_PORTSET	GPIOC_PSOR
#define GPIOC_PCOR		(*(volatile uint32_t *)0x400FF088) // Port Clear Output Register
#define GPIOC_PSOR		(*(volatile uint32_t *)0x400FF084) // Port Set Output Register

CORE_PIN13_BIT = 5
GPIOC_PCOR = 0x400FF088  # Port Clear Output Register
GPIOC_PSOR = 0x400FF084  # Port Set Output Register

proxy.mem_cpy_host_to_device(GPIOC_PSOR, np.uint32(1 << CORE_PIN13_BIT).tostring())

In [299]:
proxy.update_dma_mux_chcfg(0, DMA.MUX_CHCFG(ENBL=1, TRIG=0, SOURCE=48))
proxy.update_dma_registers(DMA.Registers(SERQ=0))


Out[299]:
0

In [298]:
proxy.update_dma_registers(DMA.Registers(CERQ=0))


Out[298]:
0

In [117]:
resolve_field_values(DMA.MUX_CHCFG.FromString(proxy.read_dma_mux_chcfg(0).tostring()))[['full_name', 'value']]


Out[117]:
full_name value
parent_name
SOURCE 54
TRIG True
ENBL True

In [114]:
print proxy.update_pit_timer_config(0, PIT.TimerConfig(LDVAL=int(48e6)))
print proxy.update_pit_timer_config(0, PIT.TimerConfig(TCTRL=PIT.R_TCTRL(TEN=True)))

pit0 = PIT.TimerConfig.FromString(proxy.read_pit_timer_config(0).tostring())
display(resolve_field_values(pit0)[['full_name', 'value']].T)


0
0
parent_name TCTRL TCTRL TCTRL TFLG
full_name LDVAL CVAL TCTRL.TIE TCTRL.TEN TCTRL.CHN TFLG.TIF
value 48000000 39153688 False True False True

In [4]:
PIT_LDVAL0 = 0x40037100  # Timer Load Value Register
PIT_CVAL0  = 0x40037104  # Current Timer Value Register
PIT_TCTRL0 = 0x40037108  # Timer Control Register
proxy.mem_cpy_host_to_device(PIT_TCTRL0, np.uint32(1).tostring())
proxy.mem_cpy_device_to_host(PIT_TCTRL0, 4).view('uint32')[0]


Out[4]:
1

In [287]:
proxy.digital_write(teensy.LED_BUILTIN, 0)

In [273]:
proxy.update_dma_registers(DMA.Registers(SSRT=0))


Out[273]:
0

In [288]:
proxy.free_all()

toggle_pin_addr = proxy.mem_alloc(4)
proxy.mem_cpy_host_to_device(toggle_pin_addr, np.uint32(1 << CORE_PIN13_BIT).tostring())

tcds_addr = proxy.mem_aligned_alloc(32, 2 * 32)
hw_tcds_addr = 0x40009000
tcd_addrs = [tcds_addr + 32 * i for i in xrange(2)]

# Create Transfer Control Descriptor configuration for first chunk, encoded
# as a Protocol Buffer message.
tcd0_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
                   BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
                   ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._32_BIT,
                                       DSIZE=DMA.R_TCD_ATTR._32_BIT),
                   NBYTES_MLNO=4,
                   SADDR=int(toggle_pin_addr),
                   SOFF=0,
                   SLAST=0,
                   DADDR=int(GPIOC_PSOR),
                   DOFF=0,
#                    DLASTSGA=0,
#                    CSR=DMA.R_TCD_CSR(START=0, DONE=False, ESG=False))
# proxy.update_dma_TCD(0, tcd0_msg)
                   DLASTSGA=int(tcd_addrs[1]),
                   CSR=DMA.R_TCD_CSR(START=0, DONE=False, ESG=True))

# # Convert Protocol Buffer encoded TCD to bytes structure.
tcd0 = proxy.tcd_msg_to_struct(tcd0_msg)

# Create binary TCD struct for each TCD protobuf message and copy to device
# memory.
for i in xrange(2):
    tcd_i = tcd0.copy()
    tcd_i['DADDR'] = [GPIOC_PSOR, GPIOC_PCOR][i]
    tcd_i['DLASTSGA'] = tcd_addrs[(i + 1) % len(tcd_addrs)]
    tcd_i['CSR'] |= (1 << 4)
    proxy.mem_cpy_host_to_device(tcd_addrs[i], tcd_i.tostring())

# Load initial TCD in scatter chain to DMA channel chosen to handle scattering.
proxy.mem_cpy_host_to_device(hw_tcds_addr, tcd0.tostring())

In [61]:
proxy.update_dma_registers(DMA.Registers(SSRT=0))


Out[61]:
0

In [ ]:
dma_channel_scatter = 0
dma_channel_i = 1
dma_channel_ii = 2

Configure ADC sample rate, etc.


In [ ]:
# Set ADC parameters
proxy.setAveraging(16, teensy.ADC_0)
proxy.setResolution(16, teensy.ADC_0)
proxy.setConversionSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.setSamplingSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.update_adc_registers(
    teensy.ADC_0,
    ADC.Registers(CFG2=ADC.R_CFG2(MUXSEL=ADC.R_CFG2.B)))

Pseudo-code to set DMA channel $i$ to be triggered by ADC0 conversion complete.

DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0  // Route ADC0 as DMA channel source.
DMAMUX0_CFGi[TRIG] = 0  // Disable periodic trigger.
DMAMUX0_CFGi[ENBL] = 1  // Enable the DMAMUX configuration for channel.

DMA_ERQ[i] = 1  // DMA request input signals and this enable request flag
                // must be asserted before a channel’s hardware service
                // request is accepted (21.3.3/394).
DMA_SERQ = i  // Can use memory mapped convenience register to set instead.

Set DMA mux source for channel 0 to ADC0


In [ ]:
DMAMUX_SOURCE_ADC0 = 40  # from `kinetis.h`
DMAMUX_SOURCE_ADC1 = 41  # from `kinetis.h`

#    DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0  // Route ADC0 as DMA channel source.
#    DMAMUX0_CFGi[TRIG] = 0  // Disable periodic trigger.
#    DMAMUX0_CFGi[ENBL] = 1  // Enable the DMAMUX configuration for channel.
proxy.update_dma_mux_chcfg(dma_channel_ii,
                           DMA.MUX_CHCFG(SOURCE=DMAMUX_SOURCE_ADC0,
                                         TRIG=False,
                                         ENBL=True))

# DMA request input signals and this enable request flag
# must be asserted before a channel’s hardware service
# request is accepted (21.3.3/394).
#    DMA_SERQ = i
proxy.update_dma_registers(DMA.Registers(SERQ=dma_channel_ii))
proxy.enableDMA(teensy.ADC_0)

In [ ]:
proxy.DMA_registers().loc['']

In [ ]:
dmamux = DMA.MUX_CHCFG.FromString(proxy.read_dma_mux_chcfg(dma_channel_ii).tostring())
resolve_field_values(dmamux)[['full_name', 'value']]

In [ ]:
adc0 = ADC.Registers.FromString(proxy.read_adc_registers(teensy.ADC_0).tostring())
resolve_field_values(adc0)[['full_name', 'value']].loc[['CFG2', 'SC1A', 'SC3']]

Analog channel list

  • List of channels to sample.
  • Map channels from Teensy references (e.g., A0, A1, etc.) to the Kinetis analog pin numbers using the adc.CHANNEL_TO_SC1A_ADC0 mapping.

In [ ]:
import re

import numpy as np
import pandas as pd
import arduino_helpers.hardware.teensy.adc as adc

# The number of samples to record for each ADC channel.
sample_count = 10

teensy_analog_channels = ['A0', 'A1', 'A0', 'A3', 'A0']
sc1a_pins = pd.Series(dict([(v, adc.CHANNEL_TO_SC1A_ADC0[getattr(teensy, v)])
                            for v in dir(teensy) if re.search(r'^A\d+', v)]))
channel_sc1as = np.array(sc1a_pins[teensy_analog_channels].tolist(), dtype='uint32')

Allocate and initialize device arrays

  • SD1A register configuration for each ADC channel in the channel_sc1as list.
    • Copy channel_sc1as list to device.
  • ADC result array
    • Initialize to zero.

In [ ]:
proxy.free_all()

N = np.dtype('uint16').itemsize * channel_sc1as.size

# Allocate source array
adc_result_addr = proxy.mem_alloc(N)

# Fill result array with zeros
proxy.mem_fill_uint8(adc_result_addr, 0, N)

# Copy channel SC1A configurations to device memory
adc_sda1s_addr = proxy.mem_aligned_alloc_and_set(4, channel_sc1as.view('uint8'))

# Allocate source array
samples_addr = proxy.mem_alloc(sample_count * N)

tcds_addr = proxy.mem_aligned_alloc(32, sample_count * 32)
hw_tcds_addr = 0x40009000
tcd_addrs = [tcds_addr + 32 * i for i in xrange(sample_count)]
hw_tcd_addrs = [hw_tcds_addr + 32 * i for i in xrange(sample_count)]

# Fill result array with zeros
proxy.mem_fill_uint8(samples_addr, 0, sample_count * N)

# Create Transfer Control Descriptor configuration for first chunk, encoded
# as a Protocol Buffer message.
tcd0_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
                   BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ITER=1),
                   ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
                                       DSIZE=DMA.R_TCD_ATTR._16_BIT),
                   NBYTES_MLNO=channel_sc1as.size * 2,
                   SADDR=int(adc_result_addr),
                   SOFF=2,
                   SLAST=-channel_sc1as.size * 2,
                   DADDR=int(samples_addr),
                   DOFF=2 * sample_count,
                   DLASTSGA=int(tcd_addrs[1]),
                   CSR=DMA.R_TCD_CSR(START=0, DONE=False, ESG=True))

# Convert Protocol Buffer encoded TCD to bytes structure.
tcd0 = proxy.tcd_msg_to_struct(tcd0_msg)

# Create binary TCD struct for each TCD protobuf message and copy to device
# memory.
for i in xrange(sample_count):
    tcd_i = tcd0.copy()
    tcd_i['SADDR'] = adc_result_addr
    tcd_i['DADDR'] = samples_addr + 2 * i
    tcd_i['DLASTSGA'] = tcd_addrs[(i + 1) % len(tcd_addrs)]
    tcd_i['CSR'] |= (1 << 4)
    proxy.mem_cpy_host_to_device(tcd_addrs[i], tcd_i.tostring())

# Load initial TCD in scatter chain to DMA channel chosen to handle scattering.
proxy.mem_cpy_host_to_device(hw_tcd_addrs[dma_channel_scatter],
                             tcd0.tostring())

print 'ADC results:', proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')
print 'Analog pins:', proxy.mem_cpy_device_to_host(adc_sda1s_addr, len(channel_sc1as) *
                                                   channel_sc1as.dtype.itemsize).view('uint32')

Configure DMA channel $i$


In [ ]:
ADC0_SC1A = 0x4003B000  # ADC status and control registers 1

sda1_tcd_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
                       BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
                       ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._32_BIT,
                                           DSIZE=DMA.R_TCD_ATTR._32_BIT),
                       NBYTES_MLNO=4,
                       SADDR=int(adc_sda1s_addr),
                       SOFF=4,
                       SLAST=-channel_sc1as.size * 4,
                       DADDR=int(ADC0_SC1A),
                       DOFF=0,
                       DLASTSGA=0,
                       CSR=DMA.R_TCD_CSR(START=0, DONE=False))

proxy.update_dma_TCD(dma_channel_i, sda1_tcd_msg)

Configure DMA channel $ii$


In [ ]:
ADC0_RA = 0x4003B010  # ADC data result register
ADC0_RB = 0x4003B014  # ADC data result register


tcd_msg = DMA.TCD(CITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
                  BITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
                  ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
                                      DSIZE=DMA.R_TCD_ATTR._16_BIT),
                  NBYTES_MLNO=2,
                  SADDR=ADC0_RA,
                  SOFF=0,
                  SLAST=0,
                  DADDR=int(adc_result_addr),
                  DOFF=2,
                  DLASTSGA=-channel_sc1as.size * 2,
                  CSR=DMA.R_TCD_CSR(START=0, DONE=False,
                                    MAJORELINK=True,
                                    MAJORLINKCH=dma_channel_scatter))

proxy.update_dma_TCD(dma_channel_ii, tcd_msg)

Trigger sample scan across selected ADC channels


In [ ]:
# Clear output array to zero.
proxy.mem_fill_uint8(adc_result_addr, 0, N)
proxy.mem_fill_uint8(samples_addr, 0, sample_count * N)

# Software trigger channel $i$ to copy *first* SC1A configuration, which
# starts ADC conversion for the first channel.
#
# Conversions for subsequent ADC channels are triggered through minor-loop
# linking from DMA channel $ii$ to DMA channel $i$ (*not* through explicit
# software trigger).
print 'ADC results:'
for i in xrange(sample_count):
    proxy.update_dma_registers(DMA.Registers(SSRT=dma_channel_i))

    # Display converted ADC values (one value per channel in `channel_sd1as` list).
    print '  Iteration %s:' % i, proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')

print ''
print 'Samples by channel:'
# Trigger once per chunk
# for i in xrange(sample_count):
# proxy.update_dma_registers(DMA.Registers(SSRT=0))
device_dst_data = proxy.mem_cpy_device_to_host(samples_addr, sample_count * N)
pd.DataFrame(device_dst_data.view('uint16').reshape(-1, sample_count).T,
             columns=teensy_analog_channels)

In [ ]: